package org.jeecg.modules.log.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import io.swagger.annotations.ApiModelProperty;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.aspect.annotation.AutoRecordLog;
import org.jeecg.common.aspect.annotation.AutoRecordStatusLog;
import org.jeecg.common.util.RedisUtil;
import org.jeecg.modules.log.entity.SysDataLog;
import org.jeecg.modules.log.mapper.SysDataLogsMapper;
import org.jeecg.modules.log.service.ISysDataLogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Field;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @Description: 系统数据日志实现类
 * @author: jeecg-boot
 */
@Slf4j
@Service("mySysDataLogServiceImpl")
public class SysDataLogServiceImpl extends ServiceImpl<SysDataLogsMapper, SysDataLog> implements ISysDataLogService {
	@Autowired
	RedisUtil redisUtil;

	private final ReentrantLock lock = new ReentrantLock();

	/**
	 * 添加数据日志
	 *
	 *
	 *
	 */
	@Transactional(rollbackFor = Exception.class,isolation = Isolation.READ_COMMITTED)
	@Override
	public void addDataLog(String tableName, String dataId, String dataContent,String type,String before,String after,String parentId,String parentTable,String logtime,String parentLogtime) throws ClassNotFoundException, IllegalAccessException {
		String versionNumber = "0";
		log.info( "保存日志",tableName,  dataId);
		if(redisUtil.hasKey(tableName+"-"+type+"-"+dataId+"-"+logtime)){
			return;
		}
		redisUtil.set(tableName+"-"+type+"-"+dataId+"-"+logtime, dataId, 120);
		String dataVersion = this.baseMapper.queryMaxDataVer(tableName, dataId);
		if(parentId != null){
			dataVersion = this.baseMapper.queryMaxDataVer(parentTable, parentId);
		}
		if(dataVersion != null ) {
			versionNumber = String.valueOf(Integer.parseInt(dataVersion)+1);
		}
		if(parentId != null && type.equals("insert")){
			updateDataLog( tableName,  dataId,  dataContent, type, before, after, parentId, parentTable,dataVersion,logtime,parentLogtime);
			return ;
		}
		SysDataLog log = new SysDataLog();
		if(type.equals("update")){
			Map map = JSONUtil.toBean(dataContent, Map.class);
			if(CollectionUtils.isEmpty(map)){
				dataContent = "";
			}
			Object oldContent = JSONUtil.toBean(before,getClass(tableName));
			Object newContent = JSONUtil.toBean(after,getClass(tableName));
			Map[] objects = differenceSet(oldContent, newContent);
			log.setOldChangeContent(objects[1]!=null?JSONUtil.toJsonStr(objects[1]):null);
			log.setNewChangeContent(objects[2]!=null?JSONUtil.toJsonStr(objects[2]):null);
		}
		log.setCreateBy("1234");
		log.setDataTable(tableName);
		log.setDataId(dataId);
		log.setDataContent(dataContent);
		log.setDataVersion(versionNumber);
		log.setType(type);
		log.setBeforeContent(before);
		log.setAfterContent(after);
		log.setParentId(parentId);
		log.setParentTable(parentTable);
		log.setTableDes(getTableDes(tableName));
		log.setLogtime(NumberUtil.parseLong(logtime) );
		log.setParentLogtime(NumberUtil.parseLong(parentLogtime));
		this.save(log);
	}



	private void updateDataLog(String tableName, String dataId, String dataContent,String type,String before,String after,String parentId,String parentTable,String versionNumber,String logtime,String parentLogtime) throws ClassNotFoundException, IllegalAccessException {
		SysDataLog dataLog = this.getOne(new LambdaQueryWrapper<SysDataLog>()
				.eq(SysDataLog::getDataId, dataId).eq(SysDataLog::getDataTable, tableName)
				.in(SysDataLog::getType, "delete").orderByDesc(SysDataLog::getId).last("limit 1"));
		if(dataLog != null){
			String deleteContent = dataLog.getAfterContent();
			Object oldContent = JSONUtil.toBean(deleteContent,getClass(tableName));
			Object newContent = JSONUtil.toBean(after,getClass(tableName));
			Map[] objects = differenceSet(oldContent, newContent);
			// 修改删除
			dataLog.setLogtime(NumberUtil.parseLong(logtime));
			dataLog.setParentLogtime(NumberUtil.parseLong(parentLogtime));
			this.updateById(dataLog);
//			this.removeById(dataLog);
//			if(CollectionUtils.isEmpty(objects[0])){
//				return ;
//			}
			this.save(SysDataLog.builder()
							.createBy("1234")
					.dataTable(tableName).dataId(dataId).dataContent(JSONUtil.toJsonStr(objects[0]))
					.dataVersion(dataLog.getDataVersion()).type("update").beforeContent(before).afterContent(after)
					.parentTable(parentTable).parentId(parentId).tableDes(getTableDes(tableName))
							.oldChangeContent(objects[1]!=null?JSONUtil.toJsonStr(objects[1]):null)
							.newChangeContent(objects[2]!=null?JSONUtil.toJsonStr(objects[2]):null)
							.logtime(NumberUtil.parseLong(logtime)).parentLogtime(NumberUtil.parseLong(parentLogtime))
					.build());
		}else{
			this.save(SysDataLog.builder()
					.createBy("1234")
					.dataTable(tableName).dataId(dataId).dataContent(dataContent)
					.dataVersion(versionNumber).type(type).beforeContent(before).afterContent(after)
					.parentTable(parentTable).parentId(parentId).tableDes(getTableDes(tableName))
					.logtime(NumberUtil.parseLong(logtime)).parentLogtime(NumberUtil.parseLong(parentLogtime))
					.build());
		}
	}

	public Map[] differenceSet(Object oldObj, Object newObj) throws IllegalAccessException {
		Map map = new HashMap();
		Map old = new HashMap();
		Map newO = new HashMap();
		Class<?> aClass = oldObj.getClass();
		Field[] olddeclaredFields = aClass.getDeclaredFields();
		Field[] newdeclaredFields = newObj.getClass().getDeclaredFields();
		AutoRecordLog annotation = aClass.getAnnotation(AutoRecordLog.class);
		String[] ignoreCloumns = annotation.ignoreCloumn();
		for (Field olddeclaredField : olddeclaredFields) {
			String name = olddeclaredField.getName();
			olddeclaredField.setAccessible(true);
			Object oldVal = olddeclaredField.get(oldObj);
			if(ArrayUtil.contains(ignoreCloumns, name)){
				continue;
			}
			for (Field newdeclaredField : newdeclaredFields) {
				String newName = newdeclaredField.getName();
				newdeclaredField.setAccessible(true);
				Object newVal = newdeclaredField.get(newObj);
				if(name.equals(newName)&& !ObjUtil.equal(oldVal,newVal)){
					if(newdeclaredField.isAnnotationPresent(ApiModelProperty.class)){
						String value = newdeclaredField.getAnnotation(ApiModelProperty.class).value();
						if(newdeclaredField.isAnnotationPresent(AutoRecordStatusLog.class)){
							String[] v = newdeclaredField.getAnnotation(AutoRecordStatusLog.class).value();
							map.put(value,v[Integer.parseInt(oldVal.toString())] +" -> "+v[Integer.parseInt(newVal.toString())]);
						}else{
							map.put(value,this.fomatData(olddeclaredField,oldVal)+" -> "+ this.fomatData(newdeclaredField,newVal) );
						}
						old.put(value,oldVal);
						newO.put(value,newVal);
					}
				}
			}
		}
		return new Map[]{map,old,newO};
	}

	private Object fomatData(Field field,Object value){
		if (field.getType() == Date.class) {
			if(value instanceof Long){
				return DateUtil.format(new Date(value.toString()), "yyyy-MM-dd HH:mm:ss");
			}else{
				return DateUtil.format((Date) value, "yyyy-MM-dd HH:mm:ss");
			}
		}
		return value;
	}


	private Class<?> getClass(String table) throws ClassNotFoundException {
		Object obj = redisUtil.get(table);
		Map map0 = BeanUtil.toBean(obj, Map.class);
		Class<?> clazz = Class.forName(map0.get("clazz").toString());
		return clazz;
	}

	private String getTableDes(String table){
		Object obj = redisUtil.get(table);
		Map map = BeanUtil.toBean(obj, Map.class);
		return (String) map.get("tableDes");
	}






}
